import * as Y from 'yjs'
import { DeltaInsert, InsertDelta } from './types'
import { deepEquals } from './object'

export function normalizeInsertDelta(delta: InsertDelta): InsertDelta {
  const normalized: InsertDelta = []

  for (const element of delta) {
    if (typeof element.insert === 'string' && element.insert.length === 0) {
      continue
    }

    const prev = normalized[normalized.length - 1]
    if (!prev || typeof prev.insert !== 'string' || typeof element.insert !== 'string') {
      normalized.push(element)
      continue
    }

    const merge =
      prev.attributes === element.attributes ||
      (!prev.attributes === !element.attributes &&
        deepEquals(prev.attributes ?? {}, element.attributes ?? {}))

    if (merge) {
      prev.insert += element.insert
      continue
    }

    normalized.push(element)
  }

  return normalized
}

export function yTextToInsertDelta(yText: Y.XmlText): InsertDelta {
  return normalizeInsertDelta(yText.toDelta()) as InsertDelta
}

export function getInsertLength({ insert }: DeltaInsert): number {
  return typeof insert === 'string' ? insert.length : 1
}

export function getInsertDeltaLength(delta: InsertDelta): number {
  return delta.reduce((curr, element) => curr + getInsertLength(element), 0)
}

export function sliceInsertDelta(delta: InsertDelta, start: number, length: number): InsertDelta {
  if (length < 1) {
    return []
  }

  let currentOffset = 0
  const sliced: InsertDelta = []
  const end = start + length

  for (let i = 0; i < delta.length; i++) {
    if (currentOffset >= end) {
      break
    }

    const element = delta[i]
    const elementLength = getInsertLength(element)

    if (currentOffset + elementLength <= start) {
      currentOffset += elementLength
      continue
    }

    if (typeof element.insert !== 'string') {
      currentOffset += elementLength
      sliced.push(element)
      continue
    }

    const startOffset = Math.max(0, start - currentOffset)
    const endOffset = Math.min(elementLength, elementLength - (currentOffset + elementLength - end))

    sliced.push({
      ...element,
      insert: element.insert.slice(startOffset, endOffset),
    })
    currentOffset += elementLength
  }

  return sliced
}
